<!DOCTYPE html>
<title>Tests fragmentDirective.items</title>
<meta charset="utf-8">
<link rel="help" href="https://wicg.github.io/ScrollToTextFragment/issues/160">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<style>
</style>
<script>
  function reset() {
    location.hash = ':~:'
  }
</script>
<body>
  <iframe src="resources/simple-frame.html"></iframe>

  <script>
    onload = () => {
      // Ensure fragmentDirective.items is initially an empty array.
      promise_test(async (t) => {
        assert_equals(document.fragmentDirective.items.length, 0, "Main frame");
        assert_equals(frames[0].document.fragmentDirective.items.length, 0, "Iframe");

        let doc = document.implementation.createHTMLDocument("New Document");
        assert_equals(doc.fragmentDirective.items.length, 0, "Unattached document");
      }, 'Initial page has empty items array');

      // Ensure non-existent directives don't create an entry in the items array.
      promise_test(async (t) => {
        reset();

        location.hash = ':~:thisisnota=validdirective';
        assert_equals(document.fragmentDirective.items.length, 0);
      }, 'Invalid directive doesn\'t create item.');

      // Ensure a valid directive does create an entry in the items array.
      promise_test(async (t) => {
        reset();

        location.hash = ':~:text=directive';
        assert_equals(document.fragmentDirective.items.length, 1, '[MainFrame] items length');
        assert_equals(document.fragmentDirective.items[0].type, 'text', '[MainFrame] item type');
        assert_equals(document.fragmentDirective.items[0].toString(), 'text=directive',
                      '[MainFrame] item toString()');
        assert_equals(frames[0].document.fragmentDirective.items.length, 0,
                      '[IFrame] items length after setting main frame directive');

        frames[0].location.hash = ':~:text=fragmentdirective';
        assert_equals(frames[0].document.fragmentDirective.items.length, 1, '[IFrame] items length');
        assert_equals(frames[0].document.fragmentDirective.items[0].type, 'text', '[IFrame] item type');
        assert_equals(frames[0].document.fragmentDirective.items[0].toString(), 'text=fragmentdirective',
                      '[IFrame] item toString()');
        assert_equals(document.fragmentDirective.items[0].toString(), 'text=directive',
                      '[MainFrame] item toString() after setting iframe\'s');
      }, 'Valid directive creates item');

      // Ensure setting an empty directive clears the items array.
      promise_test(async (t) => {
        reset();

        location.hash = ':~:text=test';
        assert_equals(document.fragmentDirective.items.length, 1, '[PRECONDITION] items length');

        location.hash = ':~:';
        assert_equals(document.fragmentDirective.items.length, 0);
      }, 'Empty fragment directive clears items');

      // Ensure the items array isn't mutable.
      promise_test(async (t) => {
        reset();

        location.hash = ':~:text=test';
        assert_equals(document.fragmentDirective.items.length, 1, '[PRECONDITION] items length');

        assert_throws_js(TypeError, () => document.fragmentDirective.items.shift());
        assert_throws_js(TypeError, () => document.fragmentDirective.items.push({}));
      }, 'items cannot be mutated');

      // Ensure that changing the hash without a fragment directive doesn't clear the items array.
      promise_test(async (t) => {
        reset();

        location.hash = ':~:text=test';
        assert_equals(document.fragmentDirective.items.length, 1, '[PRECONDITION] items length');
        assert_equals(document.fragmentDirective.items[0].toString(), 'text=test',
                      '[PRECONDITION] item toString() before setting hash');

        location.hash = 'arbitraryFragment';

        // Wait until the hashchange event fires to ensure there isn't a delayed effect.
        await new Promise( (resolve) => { window.addEventListener('hashchange', resolve); } );

        assert_equals(document.fragmentDirective.items.length, 1, 'items length');
        assert_equals(document.fragmentDirective.items[0].toString(), 'text=test',
                      'item toString() after setting hash');
      }, 'Non fragmentDirective hash change doesn\'t affect items');

      // Ensure that changing the hash with an unrelated fragment directive updates the items
      // array.
      promise_test(async (t) => {
        reset();

        location.hash = ':~:text=test';
        assert_equals(document.fragmentDirective.items.length, 1, '[PRECONDITION] items length');
        assert_equals(document.fragmentDirective.items[0].toString(), 'text=test',
                      '[PRECONDITION] item toString() before setting hash');

        location.hash = 'arbitraryFragment:~:nonexistent=directive';

        // Wait until the hashchange event fires to ensure there isn't some delayed effect.
        await new Promise( (resolve) => { window.addEventListener('hashchange', resolve); } );

        assert_equals(document.fragmentDirective.items.length, 0, 'items length');
      }, 'Unrelated fragmentDirective hash change doesn\'t affect items');

      // Ensure that changing the hash with a new fragment directive replaces existing items.
      promise_test(async (t) => {
        reset();

        location.hash = ':~:text=test';
        assert_equals(document.fragmentDirective.items.length, 1, '[PRECONDITION] items length');
        assert_equals(document.fragmentDirective.items[0].toString(), 'text=test',
                      '[PRECONDITION] item toString() before setting hash');

        location.hash = 'arbitraryFragment:~:nonexistent=directive&text=newdirective';

        assert_equals(document.fragmentDirective.items.length, 1, 'items length');
        assert_equals(document.fragmentDirective.items[0].toString(), 'text=newdirective',
                      'item toString() after setting hash');
      }, 'New fragment directive replaces existing items');

      // Ensure an invalid but existing directive clears the array.
      promise_test(async (t) => {
        assert_equals(document.fragmentDirective.items.length, 1, '[PRECONDITION] items length');
        assert_equals(document.fragmentDirective.items[0].toString(), 'text=newdirective',
                      '[PRECONDITION] item toString() after setting hash');

        location.hash = 'arbitraryFragment:~:text=';

        assert_equals(document.fragmentDirective.items.length, 0, 'items length');
      }, 'New invalid fragment directive clears items');

      // Test setting multiple directives. Ensure their relative ordering is preserved.
      promise_test(async (t) => {
        reset();

        location.hash = ':~:text=a&nonexistent=directive&text=b&text=c';

        assert_equals(document.fragmentDirective.items.length, 3, 'items length');
        assert_equals(document.fragmentDirective.items[0].toString(), 'text=a', 'item[0].toString()');
        assert_equals(document.fragmentDirective.items[1].toString(), 'text=b', 'item[1].toString()');
        assert_equals(document.fragmentDirective.items[2].toString(), 'text=c', 'item[2].toString()');

        location.hash = ':~:text=3&text=2';

        assert_equals(document.fragmentDirective.items[0].toString(), 'text=3', 'second time item[0].toString()');
        assert_equals(document.fragmentDirective.items[1].toString(), 'text=2', 'second time item[1].toString()');
      }, 'Multiple directives');

      // Test that the directive key is case sensitive.
      promise_test(async (t) => {
        reset();

        location.hash = ':~:TEXT=test';
        assert_equals(document.fragmentDirective.items.length, 0, 'items length');
      }, 'Directive names are case sensitive');

      // Test invalid key=value isn't parsed.
      promise_test(async (t) => {
        reset();

        location.hash = ':~:text=';
        assert_equals(document.fragmentDirective.items.length, 0, 'no-value items length');

        location.hash = ':~:=test';
        assert_equals(document.fragmentDirective.items.length, 0, 'no-key items length');

        location.hash = ':~:=';
        assert_equals(document.fragmentDirective.items.length, 0, 'no-key-value items length');
      }, 'Invalid Key-Value pair');
    };
  </script>
</body>
